package devenv

import (
	"context"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/fsnotify/fsnotify"
	"github.com/louloulin/dataflare/pkg/workflow"
)

// LocalRuntime represents a local development runtime
type LocalRuntime struct {
	// engine is the workflow engine
	engine *workflow.Engine
	// workflowDir is the directory containing workflow definitions
	workflowDir string
	// DeployedWorkflows is a map of deployed workflow IDs
	DeployedWorkflows map[string]string
	// runningWorkflows is a map of running workflow IDs to run IDs
	runningWorkflows map[string]string
	// watcher is the file watcher
	watcher *fsnotify.Watcher
	// mu is the mutex
	mu sync.RWMutex
	// ctx is the context
	ctx context.Context
	// cancel is the context cancel function
	cancel context.CancelFunc
	// hotReload indicates if hot reload is enabled
	hotReload bool
	// logger is the logger
	logger *log.Logger
}

// NewLocalRuntime creates a new local development runtime
func NewLocalRuntime(options ...LocalRuntimeOption) (*LocalRuntime, error) {
	// Create a workflow engine
	engine, err := workflow.NewEngine()
	if err != nil {
		return nil, fmt.Errorf("failed to create workflow engine: %w", err)
	}

	// Create a context with cancel
	ctx, cancel := context.WithCancel(context.Background())

	// Create a local runtime
	runtime := &LocalRuntime{
		engine:            engine,
		workflowDir:       "./workflows",
		DeployedWorkflows: make(map[string]string),
		runningWorkflows:  make(map[string]string),
		ctx:               ctx,
		cancel:            cancel,
		hotReload:         false,
		logger:            log.New(os.Stdout, "[LocalRuntime] ", log.LstdFlags),
	}

	// Apply options
	for _, option := range options {
		option(runtime)
	}

	return runtime, nil
}

// LocalRuntimeOption is a function that configures a LocalRuntime
type LocalRuntimeOption func(*LocalRuntime)

// WithWorkflowDir sets the workflow directory
func WithWorkflowDir(dir string) LocalRuntimeOption {
	return func(r *LocalRuntime) {
		r.workflowDir = dir
	}
}

// WithHotReload enables or disables hot reload
func WithHotReload(enabled bool) LocalRuntimeOption {
	return func(r *LocalRuntime) {
		r.hotReload = enabled
	}
}

// WithLogger sets the logger
func WithLogger(logger *log.Logger) LocalRuntimeOption {
	return func(r *LocalRuntime) {
		r.logger = logger
	}
}

// Start starts the local runtime
func (r *LocalRuntime) Start() error {
	r.mu.Lock()
	defer r.mu.Unlock()

	// Create workflow directory if it doesn't exist
	if err := os.MkdirAll(r.workflowDir, 0755); err != nil {
		return fmt.Errorf("failed to create workflow directory: %w", err)
	}

	// Start file watcher if hot reload is enabled
	if r.hotReload {
		if err := r.startFileWatcher(); err != nil {
			return fmt.Errorf("failed to start file watcher: %w", err)
		}
	}

	// Deploy all workflows in the workflow directory
	if err := r.deployAllWorkflows(); err != nil {
		return fmt.Errorf("failed to deploy workflows: %w", err)
	}

	return nil
}

// Stop stops the local runtime
func (r *LocalRuntime) Stop() error {
	r.mu.Lock()
	defer r.mu.Unlock()

	// Stop all running workflows
	for workflowID, runID := range r.runningWorkflows {
		if err := r.engine.StopWorkflow(r.ctx, workflowID, runID); err != nil {
			r.logger.Printf("Failed to stop workflow %s: %v", workflowID, err)
		}
	}

	// Stop file watcher
	if r.watcher != nil {
		r.watcher.Close()
		r.watcher = nil
	}

	// Cancel context
	r.cancel()

	// Shutdown engine
	if err := r.engine.Shutdown(); err != nil {
		return fmt.Errorf("failed to shutdown engine: %w", err)
	}

	return nil
}

// DeployWorkflow deploys a workflow from a file
func (r *LocalRuntime) DeployWorkflow(filePath string) (string, error) {
	r.mu.Lock()
	defer r.mu.Unlock()

	// Read workflow file
	data, err := os.ReadFile(filePath)
	if err != nil {
		return "", fmt.Errorf("failed to read workflow file: %w", err)
	}

	// Parse workflow definition
	definition, err := workflow.ParseDefinition(data)
	if err != nil {
		return "", fmt.Errorf("failed to parse workflow definition: %w", err)
	}

	// Deploy workflow
	workflowID, err := r.engine.DeployWorkflow(r.ctx, definition)
	if err != nil {
		return "", fmt.Errorf("failed to deploy workflow: %w", err)
	}

	// Store workflow ID
	r.DeployedWorkflows[definition.Name] = workflowID

	r.logger.Printf("Deployed workflow %s with ID %s", definition.Name, workflowID)

	return workflowID, nil
}

// StartWorkflow starts a workflow
func (r *LocalRuntime) StartWorkflow(workflowName string, parameters map[string]interface{}) (string, error) {
	r.mu.Lock()
	defer r.mu.Unlock()

	// Get workflow ID
	workflowID, ok := r.DeployedWorkflows[workflowName]
	if !ok {
		return "", fmt.Errorf("workflow %s not deployed", workflowName)
	}

	// Start workflow
	runID, err := r.engine.StartWorkflow(r.ctx, workflowID, parameters)
	if err != nil {
		return "", fmt.Errorf("failed to start workflow: %w", err)
	}

	// Store run ID
	r.runningWorkflows[workflowID] = runID

	r.logger.Printf("Started workflow %s with run ID %s", workflowName, runID)

	return runID, nil
}

// StopWorkflow stops a workflow
func (r *LocalRuntime) StopWorkflow(workflowName string) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	// Get workflow ID
	workflowID, ok := r.DeployedWorkflows[workflowName]
	if !ok {
		return fmt.Errorf("workflow %s not deployed", workflowName)
	}

	// Get run ID
	runID, ok := r.runningWorkflows[workflowID]
	if !ok {
		return fmt.Errorf("workflow %s not running", workflowName)
	}

	// Stop workflow
	if err := r.engine.StopWorkflow(r.ctx, runID); err != nil {
		return fmt.Errorf("failed to stop workflow: %w", err)
	}

	// Remove run ID
	delete(r.runningWorkflows, workflowID)

	r.logger.Printf("Stopped workflow %s", workflowName)

	return nil
}

// GetWorkflowStatus gets the status of a workflow
func (r *LocalRuntime) GetWorkflowStatus(workflowName string) (*workflow.WorkflowStatus, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	// Get workflow ID
	workflowID, ok := r.DeployedWorkflows[workflowName]
	if !ok {
		return nil, fmt.Errorf("workflow %s not deployed", workflowName)
	}

	// Get run ID
	runID, ok := r.runningWorkflows[workflowID]
	if !ok {
		return nil, fmt.Errorf("workflow %s not running", workflowName)
	}

	// Get workflow status
	status, err := r.engine.GetWorkflowStatus(r.ctx, runID)
	if err != nil {
		return nil, fmt.Errorf("failed to get workflow status: %w", err)
	}

	return status, nil
}

// GetWorkflowMetrics gets the metrics of a workflow
func (r *LocalRuntime) GetWorkflowMetrics(workflowName string) (map[string]interface{}, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	// Get workflow ID
	workflowID, ok := r.DeployedWorkflows[workflowName]
	if !ok {
		return nil, fmt.Errorf("workflow %s not deployed", workflowName)
	}

	// Get run ID
	runID, ok := r.runningWorkflows[workflowID]
	if !ok {
		return nil, fmt.Errorf("workflow %s not running", workflowName)
	}

	// Get workflow metrics
	metrics, err := r.engine.GetWorkflowMetrics(r.ctx, workflowID, runID)
	if err != nil {
		return nil, fmt.Errorf("failed to get workflow metrics: %w", err)
	}

	return metrics, nil
}

// ListWorkflows lists all deployed workflows
func (r *LocalRuntime) ListWorkflows() []string {
	r.mu.RLock()
	defer r.mu.RUnlock()

	// Get workflow names
	names := make([]string, 0, len(r.DeployedWorkflows))
	for name := range r.DeployedWorkflows {
		names = append(names, name)
	}

	return names
}

// startFileWatcher starts the file watcher
func (r *LocalRuntime) startFileWatcher() error {
	// Create a new watcher
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return fmt.Errorf("failed to create file watcher: %w", err)
	}
	r.watcher = watcher

	// Start watching for events
	go func() {
		for {
			select {
			case event, ok := <-watcher.Events:
				if !ok {
					return
				}
				// Handle file events
				r.handleFileEvent(event)
			case err, ok := <-watcher.Errors:
				if !ok {
					return
				}
				r.logger.Printf("File watcher error: %v", err)
			case <-r.ctx.Done():
				return
			}
		}
	}()

	// Add workflow directory to watcher
	if err := watcher.Add(r.workflowDir); err != nil {
		watcher.Close()
		return fmt.Errorf("failed to watch workflow directory: %w", err)
	}

	r.logger.Printf("Started file watcher for directory: %s", r.workflowDir)

	return nil
}

// handleFileEvent handles a file event
func (r *LocalRuntime) handleFileEvent(event fsnotify.Event) {
	// Check if the event is a write or create event
	if event.Op&(fsnotify.Write|fsnotify.Create) == 0 {
		return
	}

	// Check if the file is a workflow file
	if filepath.Ext(event.Name) != ".yaml" && filepath.Ext(event.Name) != ".yml" && filepath.Ext(event.Name) != ".json" {
		return
	}

	// Get workflow name from file name
	workflowName := filepath.Base(event.Name)
	workflowName = workflowName[:len(workflowName)-len(filepath.Ext(workflowName))]

	// Wait a short time to ensure the file is fully written
	time.Sleep(100 * time.Millisecond)

	// Check if the workflow is already deployed
	r.mu.RLock()
	workflowID, deployed := r.DeployedWorkflows[workflowName]
	runID, running := r.runningWorkflows[workflowID]
	r.mu.RUnlock()

	// Stop the workflow if it's running
	if deployed && running {
		if err := r.engine.StopWorkflow(r.ctx, runID); err != nil {
			r.logger.Printf("Failed to stop workflow %s: %v", workflowName, err)
		} else {
			r.mu.Lock()
			delete(r.runningWorkflows, workflowID)
			r.mu.Unlock()
			r.logger.Printf("Stopped workflow %s for hot reload", workflowName)
		}
	}

	// Deploy the workflow
	newWorkflowID, err := r.DeployWorkflow(event.Name)
	if err != nil {
		r.logger.Printf("Failed to deploy workflow %s: %v", workflowName, err)
		return
	}

	// Start the workflow if it was running
	if running {
		newRunID, err := r.engine.StartWorkflow(r.ctx, newWorkflowID, nil)
		if err != nil {
			r.logger.Printf("Failed to start workflow %s: %v", workflowName, err)
		} else {
			r.mu.Lock()
			r.runningWorkflows[newWorkflowID] = newRunID
			r.mu.Unlock()
			r.logger.Printf("Started workflow %s with run ID %s after hot reload", workflowName, newRunID)
		}
	}
}

// deployAllWorkflows deploys all workflows in the workflow directory
func (r *LocalRuntime) deployAllWorkflows() error {
	// Get all workflow files
	files, err := filepath.Glob(filepath.Join(r.workflowDir, "*.yaml"))
	if err != nil {
		return fmt.Errorf("failed to list workflow files: %w", err)
	}

	// Add YAML files
	ymlFiles, err := filepath.Glob(filepath.Join(r.workflowDir, "*.yml"))
	if err != nil {
		return fmt.Errorf("failed to list workflow files: %w", err)
	}
	files = append(files, ymlFiles...)

	// Add JSON files
	jsonFiles, err := filepath.Glob(filepath.Join(r.workflowDir, "*.json"))
	if err != nil {
		return fmt.Errorf("failed to list workflow files: %w", err)
	}
	files = append(files, jsonFiles...)

	// Deploy each workflow
	for _, file := range files {
		if _, err := r.DeployWorkflow(file); err != nil {
			r.logger.Printf("Failed to deploy workflow %s: %v", file, err)
		}
	}

	return nil
}

// GetEngine gets the workflow engine
func (r *LocalRuntime) GetEngine() *workflow.Engine {
	return r.engine
}
